/**
 * Copyright 2018 orivil.com. All rights reserved.
 * Use of this source code is governed by a MIT-style
 * license that can be found at https://mit-license.org.
 */

package mux

import (
	"fmt"
	"net/http"
	"strings"
	"unsafe"
	"gitee.com/tugoer/orivil/param"
)

type ApiDoc struct {
	Tags        []string
	Responses   Responses
	Summary     string
	Description string
}

type Parameters []*param.Parameter

type Action struct {
	// REQUIRED. action 名应当唯一
	Name string

	// REQUIRED. 定义匹配 http 请求方法, 如果不填则视为匹配 "GET" 请求
	Method string

	// REQUIRED. 定义匹配 http 请求的路径
	Route string

	// 定义参数模型. 可用于验证参数, 解析参数, 以及生成 api 文档时会根据该字段来生
	// 成适用于 OpenApi 的参数模型
	Params Parameters

	// REQUIRED. 定义路由匹配到该路径时所执行的函数
	Handler ContextHandler

	// 定义 api 文档. 如果该字段不为 nil, 则将该 action 添加进 OpenApi 文档中
	ApiDoc *ApiDoc

	// 参数解析器, 根据 Params 得到
	parser *param.Parameters

	// 前置中间件
	before []Middleware

	// 后置中间件
	after []Middleware
}

func (a *Action) IsOneOf(actions []*Action) bool {
	for _, act := range actions {
		if unsafe.Pointer(a) == unsafe.Pointer(act) {
			return true
		}
	}
	return false
}

// ContainsTags 用于判断当前 action 是否包含 tags 其中的一个或多个
func (a *Action) ContainsTags(tags ...string) bool {
	if doc := a.ApiDoc; doc != nil {
		for _, tag := range tags {
			for _, t:= range doc.Tags {
				if tag == t {
					return true
				}
			}
		}
	}
	return false
}

func (a *Action) Path() string {
	switch a.Method {
	case "":
		a.Method = http.MethodGet
	case
		http.MethodGet,
		http.MethodPost,
		http.MethodPut,
		http.MethodPatch,
		http.MethodTrace,
		http.MethodHead,
		http.MethodOptions,
		http.MethodDelete,
		http.MethodConnect:
	default:
		panic(fmt.Errorf("unsported method %s", a.Method))
	}
	path := strings.TrimLeft(a.Route, "/")
	if path == "" {
		return a.Method
	} else {
		return a.Method + "/" + path
	}
}

func (a *Action) serve(tpl TemplateHandler, ctx *Context) {
	for _, h := range a.before {
		h.Serve(ctx)
	}
	a.Handler(ctx)
	for _, h := range a.after {
		h.Serve(ctx)
	}
	if ctx.views != nil {
		if ctx.vDir != "" {
			for key, value := range ctx.views {
				ctx.views[key] = ctx.vDir + "/" + value
			}
		}
		if Env.Server.Debug {
			ctx.With("debug", true)
		}
		err := tpl.HandleViews(ctx.Request.URL.Path, ctx.views, ctx.viewData, ctx.Response)
		if err != nil {
			ctx.Error(err, ErrWarning, 0)
		}
	}
}

func (a *Action) addMiddleware(middle Middleware) {
	switch middle.(type) {
	case AfterHandler:
		a.after = append(a.after, middle)
	default:
		a.before = append(a.before, middle)
	}
}